﻿using System;
using System.IO;
using Exceptions;

namespace RSA
{
    /// <summary>
    /// Public-key BLOBs, type PUBLICKEYBLOB, are used to store public keys outside a CSP. Extended provider public-key BLOBs have the following format.
    /// </summary>
    internal struct PublicKeyBlob
    {
        /// <summary>
        /// BLOB Header
        /// </summary>
        public BlobHeader Header;

        /// <summary>
        /// RSA public key data
        /// </summary>
        public RSAPubKey RSAPubKey;

        /// <summary>
        /// The public-key modulus data
        /// </summary>
        public byte[] Modulus;

        #region FromBinary - Create and initialize structure from binary data

        /// <summary>
        /// Create and initialize structure from binary data
        /// </summary>
        /// <exception cref="CryptographicException">On validate errors</exception>
        /// <returns>Initialized structure</returns>
        public static PublicKeyBlob FromBinary(BinaryReader reader)
        {
            BlobHeader header = BlobHeader.FromBinary(reader);

            return FromBinary(reader, header);
        }

        /// <summary>
        /// Create and initialize structure from binary data with defined header
        /// </summary>
        /// <exception cref="CryptographicException">On validate errors</exception>
        /// <returns>Initialized structure</returns>
        public static PublicKeyBlob FromBinary(BinaryReader reader, BlobHeader header)
        {
            var publicKeyBlob = new PublicKeyBlob
            {
                Header = header,
                RSAPubKey = RSAPubKey.FromBinary(reader),
            };

            int modulusLength = (int)(publicKeyBlob.RSAPubKey.BitLength >> 3);

            publicKeyBlob.Modulus = new byte[modulusLength];
            reader.Read(publicKeyBlob.Modulus, 0, publicKeyBlob.Modulus.Length);

            return publicKeyBlob;
        }

        #endregion

        #region ToBinary - Serializes structure as binary data

        /// <summary>
        /// Serializes structure as binary data
        /// </summary>
        public void ToBinary(BinaryWriter writer)
        {
            Header.ToBinary(writer);
            RSAPubKey.ToBinary(writer);
            writer.Write(Modulus);
        }

        #endregion

        #region FromRSAParameters - Create and initialize structure from RSAParameters

        /// <summary>
        /// Create and initialize structure from RSAParameters
        /// </summary>
        /// <returns>Initialized structure</returns>
        /// <note>http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsaparameters.aspx</note>
        public static PublicKeyBlob FromRSAParameters(RSAParameters @params)
        {
            var publicKeyBlob = new PublicKeyBlob
            {
                Header = BlobHeader.FromRSAParameters(KeyBlobType.PublicKeyBlob),
                RSAPubKey = RSAPubKey.FromRSAParameters(@params, false),

                Modulus = new byte[@params.N.Length],
            };

            for (int i = 0; i < publicKeyBlob.Modulus.Length; i++)
            {
                publicKeyBlob.Modulus[i] = @params.N[@params.N.Length - i - 1];
            }

            return publicKeyBlob;
        }

        #endregion

        #region ToRSAParameters - Create and initialize RSAParameters structure

        /// <summary>
        /// Create and initialize RSAParameters structure
        /// </summary>
        /// <returns>Initialized structure</returns>
        /// <note>http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsaparameters.aspx</note>
        public RSAParameters ToRSAParameters()
        {
            var @params = RSAPubKey.ToRSAParameters();

            @params.N = new byte[Modulus.Length];

            for (int i = 0; i < @params.N.Length; i++)
            {
                @params.N[i] = Modulus[Modulus.Length - i - 1];
            }

            return @params;
        }

        #endregion

        #region ToString overriding

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return string.Format("Header:\r\n{0}\r\nRSAPubKey:\r\n{1}\r\nModulus: {2}", Header, RSAPubKey,
                                 BitConverter.ToString(Modulus));
        }

        #endregion

    }
}